package com.engine.salary.maintainer.salaryacct;

import com.engine.common.util.ServiceUtil;
import com.engine.core.impl.Service;
import com.engine.salary.entity.datacollection.DataCollectionEmployee;
import com.engine.salary.entity.datacollection.dto.AttendQuoteFieldListDTO;
import com.engine.salary.entity.salaryacct.bo.SalaryAcctCalculateBO;
import com.engine.salary.entity.salaryacct.bo.SalaryAcctCalculatePriorityBO;
import com.engine.salary.entity.progress.ProgressDTO;
import com.engine.salary.entity.salaryacct.po.SalaryAcctEmployeePO;
import com.engine.salary.entity.salaryacct.po.SalaryAcctRecordPO;
import com.engine.salary.entity.salaryacct.po.SalaryAcctResultPO;
import com.engine.salary.entity.salaryacct.po.SalaryAcctResultTempPO;
import com.engine.salary.entity.salaryformula.ExpressFormula;
import com.engine.salary.entity.salaryitem.po.SalaryItemPO;
import com.engine.salary.entity.salarysob.dto.SalarySobCycleDTO;
import com.engine.salary.entity.salarysob.po.SalarySobAdjustRulePO;
import com.engine.salary.entity.salarysob.po.SalarySobBackItemPO;
import com.engine.salary.entity.salarysob.po.SalarySobItemPO;
import com.engine.salary.entity.salarysob.po.SalarySobPO;
import com.engine.salary.exception.SalaryRunTimeException;
import com.engine.salary.mapper.salaryacct.SalaryAcctResultMapper;
import com.engine.salary.service.*;
import com.engine.salary.service.impl.*;
import com.engine.salary.sys.enums.TaxDeclarationFunctionEnum;
import com.engine.salary.sys.service.SalarySysConfService;
import com.engine.salary.sys.service.impl.SalarySysConfServiceImpl;
import com.engine.salary.util.SalaryEntityUtil;
import com.engine.salary.util.SalaryI18nUtil;
import com.engine.salary.util.db.MapperProxyFactory;
import com.engine.salary.util.valid.ValidUtil;
import com.google.common.collect.Lists;
import com.weaver.util.threadPool.ThreadPoolUtil;
import com.weaver.util.threadPool.constant.ModulePoolEnum;
import com.weaver.util.threadPool.entity.LocalRunnable;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import weaver.hrm.User;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.stream.Collectors;

/**
 * 薪资核算维护类
 * <p>Copyright: Copyright (c) 2022</p>
 * <p>Company: 泛微软件</p>
 *
 * @author qiantao
 * @version 1.0
 **/
@Slf4j
public class SalaryAcctManager extends Service {
    private SalaryAcctResultMapper getSalaryAcctResultMapper() {
        return MapperProxyFactory.getProxy(SalaryAcctResultMapper.class);
    }

    private SalaryAcctEmployeeService getSalaryAcctEmployeeService(User user) {
        return ServiceUtil.getService(SalaryAcctEmployeeServiceImpl.class, user);
    }

    private SalaryAcctResultService getSalaryAcctResultService(User user) {
        return ServiceUtil.getService(SalaryAcctResultServiceImpl.class, user);
    }

    private SalarySobItemService getSalarySobItemService(User user) {
        return ServiceUtil.getService(SalarySobItemServiceImpl.class, user);
    }

    private SalaryItemService getSalaryItemService(User user) {
        return ServiceUtil.getService(SalaryItemServiceImpl.class, user);
    }

    private SalarySobEmpFieldService getSalarySobEmpFieldService(User user) {
        return ServiceUtil.getService(SalarySobEmpFieldServiceImpl.class, user);
    }

    private SalarySobService getSalarySobService(User user) {
        return ServiceUtil.getService(SalarySobServiceImpl.class, user);
    }

    private SalaryAcctRecordService getSalaryAcctRecordService(User user) {
        return ServiceUtil.getService(SalaryAcctRecordServiceImpl.class, user);
    }

    private TaxAgentService getTaxAgentService(User user) {
        return ServiceUtil.getService(TaxAgentServiceImpl.class, user);
    }

    private SalaryEmployeeService getSalaryEmployeeService(User user) {
        return ServiceUtil.getService(SalaryEmployeeServiceImpl.class, user);
    }

    private SalaryFormulaService getSalaryFormulaService(User user) {
        return ServiceUtil.getService(SalaryFormulaServiceImpl.class, user);
    }

    private SalarySobAdjustRuleService getSalarySobAdjustRuleService(User user) {
        return ServiceUtil.getService(SalarySobAdjustRuleServiceImpl.class, user);
    }


    private SalaryAcctCalculateService getSalaryAcctCalculateService(User user) {
        return ServiceUtil.getService(SalaryAcctCalculateServiceImpl.class, user);
    }

    private ProgressService getSalaryAcctProgressService(User user) {
        return ServiceUtil.getService(ProgressServiceImpl.class, user);
    }

    private DataSourceTransactionManager dataSourceTransactionManager;

    private SalaryAcctResultTempService getSalaryAcctResultTempService(User user) {
        return ServiceUtil.getService(SalaryAcctResultTempServiceImpl.class, user);
    }

    private SIAccountService getSIAccountService(User user) {
        return ServiceUtil.getService(SIAccountServiceImpl.class, user);
    }

    private AttendQuoteFieldService getAttendQuoteFieldService(User user) {
        return ServiceUtil.getService(AttendQuoteFieldServiceImpl.class, user);
    }

    private SalaryAcctReportService getSalaryAcctReportService(User user) {
        return ServiceUtil.getService(SalaryAcctReportServiceImpl.class, user);
    }

    private SalarySobItemGroupService getSalarySobItemGroupService(User user) {
        return ServiceUtil.getService(SalarySobItemGroupServiceImpl.class, user);
    }

    private SalarySobBackItemService getSalarySobBackItemService(User user) {
        return ServiceUtil.getService(SalarySobBackItemServiceImpl.class, user);
    }

    private SalarySysConfService getSalarySysConfService(User user) {
        return ServiceUtil.getService(SalarySysConfServiceImpl.class, user);
    }



    /**
     * 补充核算
     * 核算已归档后，需要重新核算某一项
     */
    public void supplementAcctRecord(SalaryAcctSupplementParam param) {
        ValidUtil.doValidator(param);


        //待补算的记录
        List<SalaryAcctRecordPO> salaryAcctRecordPOS = new ArrayList<>();

        if (StringUtils.equals(param.getType(), "sob")) {

            List<Long> salarySobIds = param.getSalarySobIds();

            if (CollectionUtils.isEmpty(salarySobIds)) {
                throw new SalaryRunTimeException("补算账套为空！");
            }
            //获取账套下面所有的核算记录
            salaryAcctRecordPOS = getSalaryAcctRecordService(user).listBySalarySobIds(salarySobIds);

            if (CollectionUtils.isEmpty(salaryAcctRecordPOS)) {
                throw new SalaryRunTimeException("账套下不存在核算记录");
            }

        } else if (StringUtils.equals(param.getType(), "record")) {

            List<Long> salaryAcctRecordIds = param.getSalaryAcctRecordIds();

            salaryAcctRecordPOS = getSalaryAcctRecordService(user).listByIds(salaryAcctRecordIds);

            if (CollectionUtils.isEmpty(salaryAcctRecordPOS)) {
                throw new SalaryRunTimeException("核算记录不存在");
            }
        } else {
            throw new SalaryRunTimeException("补算方式异常！");
        }

        salaryAcctRecordPOS.forEach(salaryAcctRecordPO -> {
            param.setSalaryAcctRecordId(salaryAcctRecordPO.getId());
            calculate(param);
        });

    }

    /**
     * 补算
     *
     * @param calculateParam 薪资核算的参数
     */
    public void calculate(SalaryAcctSupplementParam calculateParam) {
        log.info("补算开始{}", calculateParam);

        //核算记录
        Long salaryAcctRecordId = calculateParam.getSalaryAcctRecordId();

        //当前登陆人员
        DataCollectionEmployee simpleEmployee = new DataCollectionEmployee();
        simpleEmployee.setEmployeeId((long) user.getUID());

        // 检查是否正在核算中
        ProgressDTO salaryAcctProgressDTO = getSalaryAcctProgressService(user).getProgress("SUPPLEMENT_ACCT_PROGRESS_" + salaryAcctRecordId);
        if (Objects.nonNull(salaryAcctProgressDTO) && salaryAcctProgressDTO.isStatus() && Optional.ofNullable(salaryAcctProgressDTO.getProgress()).orElse(BigDecimal.ZERO).compareTo(BigDecimal.ONE) < 0) {
            return;
        }
        // 初始化进度
        ProgressDTO initProgress = new ProgressDTO()
                .setTitle(SalaryI18nUtil.getI18nLabel(97515, "核算中"))
                .setTitleLabelId(97515L)
                .setTotalQuantity(NumberUtils.INTEGER_ONE)
                .setCalculatedQuantity(NumberUtils.INTEGER_ZERO)
                .setProgress(BigDecimal.ZERO)
                .setStatus(true)
                .setMessage(StringUtils.EMPTY);
        getSalaryAcctProgressService(user).initProgress("SUPPLEMENT_ACCT_PROGRESS_" + salaryAcctRecordId, initProgress);
        calculate(calculateParam, simpleEmployee);
    }


    public void calculate(SalaryAcctSupplementParam calculateParam, DataCollectionEmployee simpleEmployee) {
        Long salaryAcctRecordId = calculateParam.getSalaryAcctRecordId();
        try {
            // 1、查询薪资核算记录
            SalaryAcctRecordPO salaryAcctRecordPO = getSalaryAcctRecordService(user).getById(salaryAcctRecordId);
            if (Objects.isNull(salaryAcctRecordPO)) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(98747, "薪资核算记录不存在或已被删除"));
            }
            //查询对应账套
            SalarySobPO salarySobPO = getSalarySobService(user).getById(salaryAcctRecordPO.getSalarySobId());
            if (Objects.isNull(salarySobPO)) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(98747, "薪资账套不存在或已被删除"));
            }
            // 2、查询薪资核算记录的薪资周期、考勤周期等
            SalarySobCycleDTO salarySobCycleDTO = getSalaryAcctRecordService(user).getSalarySobCycleById(salaryAcctRecordId);
            // 3、查询薪资核算记录所用薪资账套的薪资项目副本
            List<SalarySobItemPO> salarySobItemPOS = getSalarySobItemService(user).listBySalarySobId(salaryAcctRecordPO.getSalarySobId());
            if (CollectionUtils.isEmpty(salarySobItemPOS)) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(99151, "当前所用的薪资账套未选择任何薪资项目，无法核算"));
            }
            // 回算薪资项目
            List<SalarySobBackItemPO> salarySobBackItems = Collections.emptyList();
            if (Objects.equals(salaryAcctRecordPO.getBackCalcStatus(), 1)) {
                salarySobBackItems = getSalarySobBackItemService(user).listBySalarySobId(salaryAcctRecordPO.getSalarySobId());
            }
            // 4、查询当前租户的所有薪资项目
            List<SalaryItemPO> salaryItemPOS = getSalaryItemService(user).listAll();
            // 5、查询薪资核算记录所用薪资账套的调薪计薪规则
            List<SalarySobAdjustRulePO> salarySobAdjustRulePOS = getSalarySobAdjustRuleService(user).listBySalarySobId(salaryAcctRecordPO.getSalarySobId());
            // 6、查询社保福利的所有字段
            Map<String, String> welfareColumns = getSIAccountService(user).welfareColumns();
            // 7、查询考勤引用的所有字段
            List<AttendQuoteFieldListDTO> attendQuoteFieldListDTOS = getAttendQuoteFieldService(user).listAll();

            //fixme 先全部锁定

//            //将除重新核算的项目外的所有的值都锁定
//            List<Long> salaryItemIds = calculateParam.getSalaryItemIds();
//            List<Long> lockSalaryItemIds = salaryItemPOS.stream().map(SalaryItemPO::getId).filter(id -> !salaryItemIds.contains(id)).collect(Collectors.toList());
//            Map<String, SalaryAcctResultPO> acctResults = new HashMap<>();
//            if (CollectionUtils.isNotEmpty(lockSalaryItemIds)) {
//                List<SalaryAcctResultPO> acctResultPOS = getSalaryAcctResultService(user).listBySalaryAcctRecordIds(Collections.singleton(salaryAcctRecordId));
//                acctResults = Optional.ofNullable(acctResultPOS)
//                        .orElse(new ArrayList<>())
//                        .stream()
//                        .filter(po -> lockSalaryItemIds.contains(po.getSalaryItemId()))
//                        .collect(Collectors.toMap(po -> po.getSalaryItemId() + "_" + po.getSalaryAcctEmpId(), a -> a, (a, b) -> a));
//            }


            // 8、查询公式详情
            Set<Long> formulaIds = SalaryEntityUtil.properties(salarySobItemPOS, SalarySobItemPO::getFormulaId);
            formulaIds.addAll(SalaryEntityUtil.properties(salaryItemPOS, SalaryItemPO::getFormulaId));
            formulaIds.addAll(SalaryEntityUtil.properties(salarySobBackItems, SalarySobBackItemPO::getFormulaId));
            List<ExpressFormula> expressFormulas = getSalaryFormulaService(user).listExpressFormula(formulaIds);
            // 本次运算的回算薪资项目所涉及的变量
            Set<String> issuedFieldIds = new HashSet<>();
            // 9、计算薪资项目的运算优先级
            List<List<Long>> salarySobItemsWithPriority = SalaryAcctCalculatePriorityBO.calculatePriority(salarySobItemPOS, salaryItemPOS, expressFormulas, salarySobBackItems, issuedFieldIds);
            // 10、根据id查询其他合并计税的薪资核算记录
            List<SalaryAcctRecordPO> otherSalaryAcctRecordPOS = getSalaryAcctRecordService(user).listById4OtherConsolidatedTax(salaryAcctRecordPO.getId());
            // 11、查询本次核算人员
            List<SalaryAcctEmployeePO> salaryAcctEmployeePOS = getSalaryAcctEmployeeService(user).listBySalaryAcctRecordId(salaryAcctRecordPO.getId());
            if (CollectionUtils.isEmpty(salaryAcctEmployeePOS)) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(103378, "薪资核算人员不能为空"));
            }
            // 11.1、初始化进度
            ProgressDTO initProgress = new ProgressDTO().setTitle(SalaryI18nUtil.getI18nLabel(97515, "核算中")).setTitleLabelId(97515L).setTotalQuantity(salaryAcctEmployeePOS.size() * 2 + 1).setCalculatedQuantity(0).setProgress(BigDecimal.ZERO).setStatus(true).setMessage(StringUtils.EMPTY);
            getSalaryAcctProgressService(user).initProgress("SUPPLEMENT_ACCT_PROGRESS_" + salaryAcctRecordId, initProgress);
            // 12、对薪资核算人员进行拆分
            List<List<SalaryAcctEmployeePO>> partition = Lists.partition(salaryAcctEmployeePOS, 100);
            // 12.1、监控子线程的任务执行
            CountDownLatch childMonitor = new CountDownLatch(partition.size());
            // 12.2、记录子线程的执行结果
            BlockingDeque<SalaryAcctCalculateBO.Result> calculateResults = new LinkedBlockingDeque<>(partition.size());
            // 12.3、生成本次运算的key
            String calculateKey = UUID.randomUUID().toString();
            // 12.4、是否采用系统算税
            TaxDeclarationFunctionEnum taxDeclarationFunction = getSalarySysConfService(user).getTaxDeclaration();
            // 12.4、多线程运算，运算结果存放在临时表中
            for (List<SalaryAcctEmployeePO> acctEmployeePOS : partition) {
                SalaryAcctCalculateBO salaryAcctCalculateBO = new SalaryAcctCalculateBO()
                        .setSalaryAcctRecordPO(salaryAcctRecordPO)
                        .setSalarySobPO(salarySobPO)
                        .setSalarySobCycleDTO(salarySobCycleDTO)
                        .setOtherSalaryAcctRecordPOS(otherSalaryAcctRecordPOS)
                        .setSalarySobItemPOS(salarySobItemPOS)
                        .setSalaryItemIdWithPriorityList(salarySobItemsWithPriority)
                        .setExpressFormulas(expressFormulas)
                        .setSalaryItemPOS(salaryItemPOS)
                        .setSalarySobAdjustRulePOS(salarySobAdjustRulePOS)
                        .setWelfareColumns(MapUtils.emptyIfNull(welfareColumns))
                        .setAttendQuoteFieldListDTOS(attendQuoteFieldListDTOS)
                        .setSalaryAcctEmployeePOS(acctEmployeePOS)
                        .setIssuedFieldIds(issuedFieldIds)
                        .setChildMonitor(childMonitor)
                        .setResults(calculateResults)
                        .setCalculateKey(calculateKey)
                        .setTaxDeclarationFunction(taxDeclarationFunction);
                List<SalarySobBackItemPO> finalSalarySobBackItems = salarySobBackItems;
                LocalRunnable localRunnable = new LocalRunnable() {
                    @Override
                    public void execute() {
                        getSalaryAcctCalculateService(user).calculate(salaryAcctCalculateBO, simpleEmployee, finalSalarySobBackItems);
                    }
                };
                ThreadPoolUtil.fixedPoolExecute(ModulePoolEnum.HRM, "salaryAcctCalculate", localRunnable);
            }
            // 13、等待所有子线程执行完毕
            childMonitor.await();
            // 14、判断子线程执行结果
            boolean allSuccess = calculateResults.stream().allMatch(SalaryAcctCalculateBO.Result::isStatus);
            if (!allSuccess) {
                // 薪资核算实现的线程的错误信息
                String errorMsg = calculateResults.stream().filter(result -> !result.isStatus()).map(SalaryAcctCalculateBO.Result::getErrMsg).collect(Collectors.joining("|"));
                getSalaryAcctProgressService(user).fail("SUPPLEMENT_ACCT_PROGRESS_" + salaryAcctRecordId, errorMsg);
                // 删除薪资核算临时存储表中的数据
                getSalaryAcctResultTempService(user).deleteByCalculateKey(calculateKey);
                return;
            }
            // 15、处理核算结果临时表数据
            handleSalaryAcctResultTemp(calculateParam, calculateKey);

            getSalaryAcctProgressService(user).finish("SUPPLEMENT_ACCT_PROGRESS_" + salaryAcctRecordId, true);

        } catch (Exception e) {
            log.info("薪资核算出错：{}", e.getMessage(), e);
            getSalaryAcctProgressService(user).fail("SUPPLEMENT_ACCT_PROGRESS_" + salaryAcctRecordId, SalaryI18nUtil.getI18nLabel(99642, "薪资核算出错") + ": " + e.getMessage());
        }
    }

    /**
     * 处理薪资核算临时存储表中的数据
     *
     * @param calculateParam
     * @param calculateKey
     */
    private void handleSalaryAcctResultTemp(SalaryAcctSupplementParam calculateParam, String calculateKey) {
        // 查询薪资核算结果的临时存储
        List<SalaryAcctResultTempPO> salaryAcctResultTempPOS = getSalaryAcctResultTempService(user).listByCalculateKey(calculateKey);
        // 删除原来的薪资核算结果
        List<Long> salaryItemIds = calculateParam.getSalaryItemIds();
        getSalaryAcctResultMapper().deleteBySalaryAcctRecordIdAndSalaryItemIds(calculateParam.getSalaryAcctRecordId(), salaryItemIds);
        // 保存薪资的薪资核算结果
        List<SalaryAcctResultPO> salaryAcctResultPOS = salaryAcctResultTempPOS.stream()
                .filter(salaryAcctResultTempPO -> salaryItemIds.contains(salaryAcctResultTempPO.getSalaryItemId()))
                .map(e -> {
                    SalaryAcctResultPO salaryAcctResult = new SalaryAcctResultPO();
                    BeanUtils.copyProperties(e, salaryAcctResult);
                    return salaryAcctResult;
                }).collect(Collectors.toList());
        getSalaryAcctResultService(user).batchSave(salaryAcctResultPOS);
        // 删除薪资核算临时存储表中的数据
        getSalaryAcctResultTempService(user).deleteByCalculateKey(calculateKey);
    }


}
